﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace Koopakiller.Numerics
{
    /// <summary>
    /// Represent a set.
    /// </summary>
    public sealed class Set
    {
        /// <summary>
        /// Adds a new number to the set.
        /// </summary>
        /// <param name="number">The number to add.</param>
        public void Add(double number)
        {
            this.includeNumber.Add(number);
        }
        /// <summary>
        /// Adds a new interval to the set.
        /// </summary>
        /// <param name="interval">The interval to add.</param>
        public void Add(Interval interval)
        {
            this.includeInterval.Add(interval);
        }

        /// <summary>
        /// Removes a number from the set.
        /// </summary>
        /// <param name="number">The number to remove.</param>
        public void Remove(double number)
        {
            this.excludeNumber.Add(number);
        }
        /// <summary>
        /// Remove a interval from the set.
        /// </summary>
        /// <param name="interval">The interval to remove.</param>
        public void Remove(Interval interval)
        {
            this.excludeInterval.Add(interval);
        }

        List<double> includeNumber = new List<double>();
        List<double> excludeNumber = new List<double>();
        List<Interval> includeInterval = new List<Interval>();
        List<Interval> excludeInterval = new List<Interval>();

        /// <summary>
        /// Gets or sets a number class that represent the base of this set.
        /// </summary>
        public NumberClass BaseClass { get; set; }

        /// <summary>
        /// Try to short the set.
        /// </summary>
        public void Compress()
        {
            //? remove doubles
            foreach (var num in this.excludeNumber)
                this.includeNumber.Remove(num);
            foreach (var num in this.excludeInterval)
                this.includeInterval.Remove(num);

            #region remove includeNumber because intervals
            foreach (var interval in this.excludeInterval)
            {
                for (int i = 0; i < includeNumber.Count; ++i)
                {
                    if (interval.ContainsElement(includeNumber[i]))
                    {
                        includeNumber.RemoveAt(i);
                        --i;
                    }
                }
            }
            #endregion

            #region combine intervals
            for (int i = 0; i < this.includeInterval.Count; ++i)
                for (int j = 0; j < this.includeInterval.Count; ++j)
                    if (i != j)
                    {
                        if (!((includeInterval[i].Maximum < includeInterval[j].Minimum && includeInterval[i].Minimum < includeInterval[j].Minimum) ||
                             (includeInterval[i].Minimum > includeInterval[j].Maximum && includeInterval[i].Maximum < includeInterval[j].Maximum))) //? are intervals in different ranges?
                        {
                            //!? not a test in another direction, because i|j move to j|i

                            if (((includeInterval[i].Minimum > includeInterval[j].Minimum) ||
                                 (includeInterval[i].Minimum == includeInterval[j].Minimum && (includeInterval[j].IncludeMinimum || !includeInterval[i].IncludeMinimum))) &&
                                ((includeInterval[i].Maximum < includeInterval[j].Maximum) ||
                                 (includeInterval[i].Maximum == includeInterval[j].Maximum && (includeInterval[j].IncludeMaximum || !includeInterval[i].IncludeMaximum))))
                            {
                                //? i c j
                                this.includeInterval.RemoveAt(i);
                                --i;// do not skip next interval
                                --j;
                                if (i < 0) i = 0;
                                if (j < 0) j = 0;
                                continue;
                            }

                            if ((includeInterval[i].Minimum < includeInterval[j].Maximum && includeInterval[i].Maximum > includeInterval[j].Maximum && includeInterval[j].Minimum < includeInterval[i].Minimum) ||
                                (includeInterval[i].Minimum == includeInterval[j].Maximum && (includeInterval[i].IncludeMinimum || includeInterval[j].IncludeMaximum)))
                            {
                                //? overlap: [ j [ i + j ] i ]
                                Interval iv = new Interval();
                                iv.Minimum = includeInterval[j].Minimum;
                                iv.IncludeMinimum = includeInterval[j].IncludeMinimum;
                                iv.Maximum = includeInterval[i].Maximum;
                                iv.IncludeMaximum = includeInterval[i].IncludeMaximum;

                                includeInterval[i] = iv;//replace one interval
                                includeInterval.RemoveAt(j);//remove the other 
                                --i;// do not skip next interval
                                --j;
                                if (i < 0) i = 0;
                                if (j < 0) j = 0;
                                continue;
                            }

                            if (includeInterval[i].Minimum == includeInterval[j].Maximum && !includeInterval[i].IncludeMinimum && !includeInterval[j].IncludeMaximum)
                            {
                                //? [ j ] x [ i ]
                                Interval iv = new Interval();
                                iv.Minimum = includeInterval[j].Minimum;
                                iv.IncludeMinimum = includeInterval[j].IncludeMinimum;
                                iv.Maximum = includeInterval[i].Maximum;
                                iv.IncludeMaximum = includeInterval[i].IncludeMaximum;

                                excludeNumber.Add(includeInterval[j].Maximum);//? except 'x'

                                includeInterval[i] = iv;//replace one interval
                                includeInterval.RemoveAt(j);//remove the other 
                                --i;// do not skip next interval
                                --j;
                                if (i < 0) i = 0;
                                if (j < 0) j = 0;
                                continue;
                            }
                        }
                    }
            #endregion

        }

        #region override

        /// <summary>
        /// Create a string with the value of this set as content.
        /// </summary>
        public override string ToString()
        {
            string baseClass = "";
            if (!this.BaseClass.ContainsNZP || !this.BaseClass.ContainsReals)
                this.BaseClass.ToString();

            string inclIntervals = string.Join(" ∪ ", this.includeInterval.Select(x => x.ToString()));
            string exclIntervals = string.Join(" ∪ ", this.excludeInterval.Select(x => x.ToString()));

            string inclNumbers = string.Join(" | ", this.includeNumber.Select(x => x.ToString()));
            string exclNumbers = string.Join(" | ", this.excludeNumber.Select(x => x.ToString()));

            List<string> incl = new List<string>();
            if (string.IsNullOrEmpty(baseClass))
                incl.Add(baseClass);
            if (string.IsNullOrEmpty(inclIntervals))
                incl.Add(inclIntervals);
            if (string.IsNullOrEmpty(inclNumbers))
                incl.Add(inclNumbers);

            List<string> excl = new List<string>();
            if (string.IsNullOrEmpty(exclIntervals))
                excl.Add(exclIntervals);
            if (string.IsNullOrEmpty(exclNumbers))
                excl.Add(exclNumbers);

            string incls = string.Join(" ∪ ", incl);
            string excls = string.Join(" ∪ ", excl);

            if (string.IsNullOrEmpty(incls))//? no includes
                return "∅";

            if (string.IsNullOrEmpty(excls))
                return incls;//? without excludes
            else
                if (excl.Count > 1 || this.excludeInterval.Count > 1)
                    return inclIntervals + "∖ ( " + excls + " )";//? more than 1 elements > brackets
                else
                    return inclIntervals + "∖ " + excls;
        }

        /// <summary>
        /// Gets a hash code for this <see cref="Set"/>.
        /// </summary>
        /// <returns>The hash code of this instance.</returns>
        public override int GetHashCode()
        {
            return this.includeNumber.Select(x => x.GetHashCode()).Xor()
                 ^ this.excludeNumber.Select(x => x.GetHashCode()).Xor()
                 ^ this.includeInterval.Select(x => x.GetHashCode()).Xor()
                 ^ this.excludeInterval.Select(x => x.GetHashCode()).Xor()
                 ^ this.BaseClass.GetHashCode();
        }

        /// <summary>
        /// Determines whether a provided <see cref="System.Object"/> is equivalent to the current <see cref="Set"/>.
        /// </summary>
        /// <param name="obj">An object to test of equality.</param>
        /// <returns><c>true</c> if <paramref name="obj"/> is an instance of <see cref="Set"/> and the values are equivalent; otherwise, <c>false</c>.</returns>
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        #endregion
    }

    /// <summary>
    /// Represent a interval.
    /// </summary>
    public struct Interval
    {
        /// <summary>
        /// Gets or sets the minimum of the interval.
        /// </summary>
        public double Minimum { get; set; }
        /// <summary>
        /// Gets or sets the maximum of the interval.
        /// </summary>
        public double Maximum { get; set; }
        /// <summary>
        /// Gets or sets a value that indicates whether this instance include the <see cref="Minimum"/>.
        /// </summary>
        public bool IncludeMinimum { get; set; }
        /// <summary>
        /// Gets or sets a value that indicates whether this instance include the <see cref="Maximum"/>.
        /// </summary>
        public bool IncludeMaximum { get; set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="Interval"/> structure. 
        /// </summary>
        /// <param name="min">The minimum of the interval.</param>
        /// <param name="max">The maximum of the interval.</param>
        public Interval(double min, double max)
            : this()
        {
            this.Minimum = min;
            this.Maximum = max;
        }
        /// <summary>
        /// Initializes a new instance of the <see cref="Interval"/> structure. 
        /// </summary>
        /// <param name="min">The minimum of the interval.</param>
        /// <param name="max">The maximum of the interval.</param>
        /// <param name="inclMin"><c>true</c>, if the interval include <paramref name="min"/>.</param>
        /// <param name="inclMax"><c>true</c>, if the interval include <paramref name="max"/>.</param>
        public Interval(double min, double max, bool inclMin, bool inclMax)
            : this(min, max)
        {
            this.IncludeMinimum = inclMin;
            this.IncludeMaximum = inclMax;
        }

        #region override

        /// <summary>
        /// Create a string with the value of this set as content.
        /// </summary>
        public override string ToString()
        {
            return string.Format("{0} {1} | {2} {3}", this.IncludeMinimum ? "[" : "]",
                                                      this.Minimum,
                                                      this.Maximum,
                                                      this.IncludeMaximum ? "]" : "[");
        }

        /// <summary>
        /// Gets a hash code for this <see cref="Interval"/>.
        /// </summary>
        /// <returns>The hash code of this instance.</returns>
        public override int GetHashCode()
        {
            return this.Minimum.GetHashCode() ^ this.Maximum.GetHashCode() ^ this.IncludeMinimum.GetHashCode() ^ this.IncludeMaximum.GetHashCode();
        }

        /// <summary>
        /// Determines whether a provided <see cref="System.Object"/> is equivalent to the current <see cref="Interval"/>.
        /// </summary>
        /// <param name="obj">An object to test of equality.</param>
        /// <returns><c>true</c> if <paramref name="obj"/> is an instance of <see cref="Interval"/> and the values are equivalent; otherwise, <c>false</c>.</returns>
        public override bool Equals(object obj)
        {
            return obj is Interval && ((Interval)obj).Minimum == this.Minimum && ((Interval)obj).Maximum == this.Maximum &&
                                      ((Interval)obj).IncludeMinimum == this.IncludeMinimum && ((Interval)obj).IncludeMaximum == this.IncludeMaximum;
        }

        #endregion

        /// <summary>
        /// Check if this instance contains a value.
        /// </summary>
        /// <param name="number">The number to check.</param>
        /// <returns><c>true</c> if this instance contains <paramref name="number"/>; otherwise, <c>false</c>.</returns>
        public bool ContainsElement(double number)
        {
            return (number > this.Minimum || (number == this.Minimum && this.IncludeMinimum))
                && (number < this.Maximum || (number == this.Maximum && this.IncludeMaximum));
        }
    }

    /// <summary>
    /// Represents a number class.
    /// </summary>
    public sealed class NumberClass : INotifyPropertyChanged
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="NumberClass"/> class.
        /// </summary>
        public NumberClass()
        {
            this.UpdateText();
        }

        /// <summary>
        /// Gets or sets a value indicating whether this number class include the zero.
        /// </summary>
        public bool ContainsZero
        {
            get { return this._ContainsZero; }
            set
            {
                if (this._ContainsZero != value)
                {
                    this._ContainsZero = value;
                    this.UpdateText();
                    this.OnPropertyChanged("ContainsZero");
                    this.OnPropertyChanged("ContainsNZP");
                }
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether this number class include positive numbers.
        /// </summary>
        public bool ContainsPositives
        {
            get { return this._ContainsPositives; }
            set
            {
                if (this._ContainsPositives != value)
                {
                    this._ContainsPositives = value;
                    this.UpdateText();
                    this.OnPropertyChanged("ContainsPositives");
                    this.OnPropertyChanged("ContainsNZP");
                }
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether this number class include negative numbers.
        /// </summary>
        public bool ContainsNegatives
        {
            get { return this._ContainsNegatives; }
            set
            {
                if (this._ContainsNegatives != value)
                {
                    this._ContainsNegatives = value;
                    this.UpdateText();
                    this.OnPropertyChanged("ContainsNegatives");
                    this.OnPropertyChanged("ContainsNZP");
                }
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether this number class include integers.
        /// </summary>
        public bool ContainsIntegers
        {
            get { return this._ContainsIntegers; }
            set
            {
                if (this._ContainsIntegers != value)
                {
                    this._ContainsIntegers = value;
                    this.UpdateText();
                    this.OnPropertyChanged("ContainsIntegers");
                }
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether this number class include rational numbers.
        /// </summary>
        public bool ContainsRationals
        {
            get { return this._ContainsRationals; }
            set
            {
                if (this._ContainsRationals != value)
                {
                    this._ContainsRationals = value;
                    this.UpdateText();
                    this.OnPropertyChanged("ContainsRationals");
                }
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether this number class include real numbers.
        /// </summary>
        public bool ContainsReals
        {
            get { return this._ContainsReals; }
            set
            {
                if (this._ContainsReals != value)
                {
                    this._ContainsReals = value;
                    this.UpdateText();
                    this.OnPropertyChanged("ContainsReals");
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether this number class include positive and negative numbers and zero.
        /// </summary>
        public bool ContainsNZP
        {
            get
            {
                return this._ContainsNegatives && this._ContainsZero && this._ContainsPositives;
            }
        }

        //? standard number class: real
        private bool _ContainsZero = true;
        private bool _ContainsPositives = true;
        private bool _ContainsNegatives = true;
        private bool _ContainsIntegers = true;
        private bool _ContainsRationals = true;
        private bool _ContainsReals = true;

        private string text = "";
        /// <summary>
        /// Gets a string that represents the number class.
        /// </summary>
        public string Text
        {
            get
            {
                return text;
            }
        }

        private void UpdateText()
        {
            text = ToString();
            OnPropertyChanged("Text");
        }

        #region override

        /// <summary>
        /// Gets a string that represents the number class.
        /// </summary>
        public override string ToString()
        {
            if (!ContainsNegatives && !ContainsPositives)
                if (ContainsZero)
                    return "{ 0 }";
                else
                    return "∅";

            string chr = "";
            if (ContainsReals)
                chr = "ℝ";
            else
                if (ContainsRationals)
                    chr = "ℚ";
                else
                    if (ContainsIntegers)
                        chr = "ℤ";
            if (chr == "")
                return "∅";

            if (!ContainsNegatives && chr == "ℤ")
            {
                chr = "ℕ";  // integer without negatives = natural number
                if (ContainsZero)
                    chr += "₀";
            }
            else
            {
                if (ContainsZero && !(ContainsPositives && ContainsNegatives))
                {
                    chr += "₀";
                    if (ContainsNegatives && !ContainsPositives)
                        chr += "⁻";
                    if (ContainsPositives && !ContainsNegatives)
                        chr += "⁺";
                }
                else
                {
                    if (ContainsNegatives && !ContainsPositives)
                        chr += "⁻";
                    if (ContainsPositives && !ContainsNegatives)
                        chr += "⁺";
                    if (ContainsPositives && ContainsNegatives && !ContainsZero)
                        chr += "∖₀";
                }
            }

            return chr;
        }

        /// <summary>
        /// Gets a hash code for this <see cref="NumberClass"/>.
        /// </summary>
        /// <returns>The hash code of this instance.</returns>
        public override int GetHashCode()
        {
            return ContainsIntegers.GetHashCode() ^ ContainsReals.GetHashCode() ^ ContainsNegatives.GetHashCode()
                 ^ ContainsPositives.GetHashCode() ^ ContainsRationals.GetHashCode() ^ ContainsZero.GetHashCode();
        }

        /// <summary>
        /// Determines whether a provided <see cref="System.Object"/> is equivalent to the current <see cref="NumberClass"/>.
        /// </summary>
        /// <param name="obj">An object to test of equality.</param>
        /// <returns><c>true</c> if <paramref name="obj"/> is an instance of <see cref="NumberClass"/> and the values are equivalent; otherwise, <c>false</c>.</returns>
        public override bool Equals(object obj)
        {
            return obj is NumberClass &&
                ((NumberClass)obj).ContainsIntegers == this.ContainsIntegers && ((NumberClass)obj).ContainsReals == this.ContainsReals &&
                ((NumberClass)obj).ContainsNegatives == this.ContainsNegatives && ((NumberClass)obj).ContainsPositives == this.ContainsPositives &&
                ((NumberClass)obj).ContainsRationals == this.ContainsRationals && ((NumberClass)obj).ContainsZero == this.ContainsZero;
        }

        #endregion

        #region INotifyPropertyChanged Members

        private void OnPropertyChanged(string propertyName)
        {
            var evt = this.PropertyChanged;
            if (evt != null)
                evt(this, new PropertyChangedEventArgs(propertyName));
        }

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}
